/* CS144
 *
 * Parser skeleton for processing item-???.xml files. Must be compiled in
 * JDK 1.5 or above.
 *
 * Instructions:
 *
 * This program processes all files passed on the command line (to parse
 * an entire diectory, type "java MyParser myFiles/*.xml" at the shell).
 *
 * At the point noted below, an individual XML file has been parsed into a
 * DOM Document node. You should fill in code to process the node. Java's
 * interface for the Document Object Model (DOM) is in package
 * org.w3c.dom. The documentation is available online at
 *
 * http://java.sun.com/j2se/1.5.0/docs/api/index.html
 *
 * A tutorial of Java's XML Parsing can be found at:
 *
 * http://java.sun.com/webservices/jaxp/
 *
 * Some auxiliary methods have been written for you. You may find them
 * useful.
 */

package edu.ucla.cs.cs144;

import java.io.*;
import java.text.*;
import java.util.*;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.FactoryConfigurationError;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.Element;
import org.w3c.dom.Text;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.ErrorHandler;

import edu.ucla.cs.cs144.Tag;


class MyParser {
	static final String[] months = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
	static HashMap<String, Integer> categories = new HashMap<String, Integer>();
	
	static int bids = 0;
	static int category_id = 0;
	
	static final String FILE_ITEMS = "temp_items.csv";
	static final String FILE_CATEGORIES = "temp_categories.csv";
	static final String FILE_CATITEMS = "temp_catitems.csv";
	static final String FILE_BIDS = "temp_bids.csv";
	static final String FILE_USERS = "temp_users.csv";

	static final class Attribute {
		static final String ITEMID = "ItemID";
		static final String RATING = "Rating";	
		static final String USERID = "UserID";
	}
	
    static final String columnSeparator = "|*|";
    static DocumentBuilder builder;
    
    static final String[] typeName = {
	"none",
	"Element",
	"Attr",
	"Text",
	"CDATA",
	"EntityRef",
	"Entity",
	"ProcInstr",
	"Comment",
	"Document",
	"DocType",
	"DocFragment",
	"Notation",
    };
    
    static class MyErrorHandler implements ErrorHandler {
        
        public void warning(SAXParseException exception)
        throws SAXException {
            fatalError(exception);
        }
        
        public void error(SAXParseException exception)
        throws SAXException {
            fatalError(exception);
        }
        
        public void fatalError(SAXParseException exception)
        throws SAXException {
            exception.printStackTrace();
            System.out.println("There should be no errors " +
                               "in the supplied XML files.");
            System.exit(3);
        }
        
    }
    
    /* Non-recursive (NR) version of Node.getElementsByTagName(...)
     */
    static Element[] getElementsByTagNameNR(Element e, String tagName) {
        Vector< Element > elements = new Vector< Element >();
        Node child = e.getFirstChild();
        while (child != null) {
            if (child instanceof Element && child.getNodeName().equals(tagName))
            {
                elements.add( (Element)child );
            }
            child = child.getNextSibling();
        }
        Element[] result = new Element[elements.size()];
        elements.copyInto(result);
        return result;
    }
    
    /* Returns the first subelement of e matching the given tagName, or
     * null if one does not exist. NR means Non-Recursive.
     */
    static Element getElementByTagNameNR(Element e, String tagName) {
        Node child = e.getFirstChild();
        while (child != null) {
            if (child instanceof Element && child.getNodeName().equals(tagName))
                return (Element) child;
            child = child.getNextSibling();
        }
        return null;
    }
    
    /* Returns the text associated with the given element (which must have
     * type #PCDATA) as child, or "" if it contains no text.
     */
    static String getElementText(Element e) {
        if (e.getChildNodes().getLength() == 1) {
            Text elementText = (Text) e.getFirstChild();
            return elementText.getNodeValue();
        }
        else
            return "";
    }
    
    /* Returns the text (#PCDATA) associated with the first subelement X
     * of e with the given tagName. If no such X exists or X contains no
     * text, "" is returned. NR means Non-Recursive.
     */
    static String getElementTextByTagNameNR(Element e, String tagName) {
        Element elem = getElementByTagNameNR(e, tagName);
        if (elem != null)
            return getElementText(elem);
        else
            return "";
    }
    
    /* Returns the amount (in XXXXX.xx format) denoted by a money-string
     * like $3,453.23. Returns the input if the input is an empty string.
     */
    static String strip(String money) {
        if (money.equals(""))
            return money;
        else {
            double am = 0.0;
            NumberFormat nf = NumberFormat.getCurrencyInstance(Locale.US);
            try { am = nf.parse(money).doubleValue(); }
            catch (ParseException e) {
                System.out.println("This method should work for all " +
                                   "money values you find in our data.");
                System.exit(20);
            }
            nf.setGroupingUsed(false);
            return nf.format(am).substring(1);
        }
    }
	
	/* Takes in a date string from the eBay data and converts it into a format that MySQL can understand and parse
	*/
	static String convertToSQL(String date) {
		int month = 0;
		int century = 20;
		
		// Find the month
		for (month = 0; month < months.length; month++)
			if (months[month] == date.substring(0, 3))
				break;
				
		// Naively get the century
		if ((new Integer(date.substring(7, 9))).intValue() > 11)
			century = 19;

		// Format the output
		String output = "";
		output += String.valueOf(century);
		output += date.substring(7, 9);
		output += "-";
		if (month < 10)
			output += "0";
		output += String.valueOf(month);
		output += "-";
		output += date.substring(4, 6);
		output += date.substring(9);
		
		return output;
	}
    
    /* Process one items-???.xml file.
     */
    static void processFile(File xmlFile) {
        Document doc = null;
        try {
            doc = builder.parse(xmlFile);
        }
        catch (IOException e) {
            e.printStackTrace();
            System.exit(3);
        }
        catch (SAXException e) {
            System.out.println("Parsing error on file " + xmlFile);
            System.out.println("  (not supposed to happen with supplied XML files)");
            e.printStackTrace();
            System.exit(3);
        }
        
        /* At this point 'doc' contains a DOM representation of an 'Items' XML
         * file. Use doc.getDocumentElement() to get the root Element. */
        System.out.println("Successfully parsed - " + xmlFile);
        
        /* Fill in code here (you will probably need to write auxiliary
            methods). */
		
		try {
			// PrintWriter example taken from
			//   http://www.cs.carleton.edu/faculty/dmusican/cs117w02/iocheat.html
			PrintWriter file_items = new PrintWriter(new FileWriter(FILE_ITEMS, true));
			PrintWriter file_categories = new PrintWriter(new FileWriter(FILE_CATEGORIES, true));
			PrintWriter file_catitems = new PrintWriter(new FileWriter(FILE_CATITEMS, true));
			PrintWriter file_bids = new PrintWriter(new FileWriter(FILE_BIDS, true));
			PrintWriter file_users = new PrintWriter(new FileWriter(FILE_USERS, true));
		
			Element items = doc.getDocumentElement();
			Node curr = null;
			
			// Get the first Item node if it exists
			if (items.getElementsByTagName(Tag.ITEM).getLength() != 0)
				curr = items.getElementsByTagName(Tag.ITEM).item(0);
			else
				return;
			
			// Iterate through the nodes
			do {
				// Output the ItemID
				String itemid = curr.getAttributes().getNamedItem(Attribute.ITEMID).getNodeValue();
				file_items.append("\"" + itemid + "\",");
				
				Node n = curr.getFirstChild();
				
				String seller_location = "";
				String seller_country = "";
				
				do {
					if (n.getNodeName().equals(Tag.CATEGORY)) {
						// Parse the Categories into a single list
						do {
							// Find out the CategoryID of the current Category
							int curr_id = 0;
							if (!categories.containsKey(n.getFirstChild().getNodeValue())) {
								categories.put(n.getFirstChild().getNodeValue(), new Integer(category_id));
								
								// Append the Category file with the new Category
								file_categories.append("\"" + String.valueOf(category_id) + "\",\"" + n.getFirstChild().getNodeValue() + "\"\n");
								
								curr_id = category_id;
								category_id++;
							} else {
								curr_id = categories.get(n.getFirstChild().getNodeValue());
							}

							// Append the file and iterate
							file_catitems.append("\""
								+ itemid + "\",\""
								+ String.valueOf(curr_id) + "\"\n");
							n = n.getNextSibling();
						} while (n != null && n.getNodeName().equals(Tag.CATEGORY));
						
						// Rewind one Node so that when we step forward in the main loop, we don't skip a Node
						n = n.getPreviousSibling();
					} else if (n.getNodeName().equals(Tag.BIDS)) {
						// Parse the Bids
						Node curr_bid = n.getFirstChild();
						
						if (curr_bid != null) {
							do {
								Node bidder = curr_bid.getFirstChild();
								Node time = bidder.getNextSibling();
								Node amount = time.getNextSibling();
								
								Node location = bidder.getFirstChild();
								Node country = bidder.getLastChild();
								
								String str_location = "";
								String str_country = "";
								
								// Check if the Location and Country exist
								if (location != null)
									str_location = location.getFirstChild().getNodeValue();
								if (country != null)
									str_country = country.getFirstChild().getNodeValue();
								
								// Append the files and iterate
								file_bids.append("\"" + itemid + "\",\"" 
									+ bidder.getAttributes().getNamedItem(Attribute.USERID).getNodeValue() + "\",\""
									+ convertToSQL(time.getFirstChild().getNodeValue()) + "\",\""
									+ strip(amount.getFirstChild().getNodeValue()) + "\"\n");
								file_users.append("\""
									+ bidder.getAttributes().getNamedItem(Attribute.USERID).getNodeValue() + "\",\""
									+ bidder.getAttributes().getNamedItem(Attribute.RATING).getNodeValue() + "\",\""
									+ str_location + "\",\""
									+ str_country + "\"\n");
								
								curr_bid = curr_bid.getNextSibling();
							} while (curr_bid != null && curr_bid.getNodeName().equals(Tag.Bids.BID));
						}
					} else if (n.getNodeName().equals(Tag.SELLER)) {
						// Seller Node
						file_users.append("\""
							+ n.getAttributes().getNamedItem(Attribute.USERID).getNodeValue() + "\",\""
							+ n.getAttributes().getNamedItem(Attribute.RATING).getNodeValue() + "\",\""
							+ seller_location + "\",\""
							+ seller_country + "\"\n");
						file_items.append("\"" + n.getAttributes().getNamedItem(Attribute.USERID).getNodeValue() + "\",");
					} else if (n.getNodeName().equals(Tag.CURRENTLY) || n.getNodeName().equals(Tag.BUY_PRICE) || n.getNodeName().equals(Tag.FIRST_BID)) {
						// Money node
						if (n.getFirstChild() != null)
							file_items.append("\"" + strip(n.getFirstChild().getNodeValue()) + "\",");
							
						// Special case:  Buy_Price isn't there so we must fill it in manually with a blank space
						if (n.getNodeName().equals(Tag.CURRENTLY) && !n.getNextSibling().getNodeName().equals(Tag.BUY_PRICE))
							file_items.append("\"\",");
					} else if (n.getNodeName().equals(Tag.STARTED) || n.getNodeName().equals(Tag.ENDS)) {
						// Date node
						file_items.append("\"" + convertToSQL(n.getFirstChild().getNodeValue()) + "\",");
					} else if (n.getNodeName().equals(Tag.LOCATION)) {
						// Location node:  save the location to be stored with the user data
						seller_location = n.getFirstChild().getNodeValue();
					} else if (n.getNodeName().equals(Tag.COUNTRY)) {
						// Country node:  save the country to be stored with the user data
						seller_country = n.getFirstChild().getNodeValue();
					} else {
						// Normal node with just text and no attributes
						if (n.getFirstChild() != null)
						{
							String desc = n.getFirstChild().getNodeValue();
							
							if (desc.length() > 4000)
							{
								desc = desc.substring(0, 4000);
							}
							
							file_items.append("\"" + desc.replaceAll("\"", "\\\\\"") + "\"");
						}
						else
							file_items.append("\"\"");
							
						if (!n.getNodeName().equals(Tag.DESCRIPTION))
							// Only put in a comma if we're not on the Description
							file_items.append(",");
					}
				} while ((n = n.getNextSibling()) != null);
				file_items.append("\n");
			} while ((curr = curr.getNextSibling()) != null);

			file_items.close();
			file_categories.close();
			file_catitems.close();
			file_bids.close();
			file_users.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
        
        /**************************************************************/
        
    }
    
    public static void main (String[] args) {
        if (args.length == 0) {
            System.out.println("Usage: java MyParser [file] [file] ...");
            System.exit(1);
        }
        
        /* Initialize parser. */
        try {
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            factory.setValidating(false);
            factory.setIgnoringElementContentWhitespace(true);      
            builder = factory.newDocumentBuilder();
            builder.setErrorHandler(new MyErrorHandler());
        }
        catch (FactoryConfigurationError e) {
            System.out.println("unable to get a document builder factory");
            System.exit(2);
        } 
        catch (ParserConfigurationException e) {
            System.out.println("parser was unable to be configured");
            System.exit(2);
        }
		
		// Clear/initialize all the temporary load files
		try {
			PrintWriter file_items = new PrintWriter(new FileWriter(FILE_ITEMS));
			PrintWriter file_categories = new PrintWriter(new FileWriter(FILE_CATEGORIES));
			PrintWriter file_catitems = new PrintWriter(new FileWriter(FILE_CATITEMS));
			PrintWriter file_bids = new PrintWriter(new FileWriter(FILE_BIDS));
			PrintWriter file_users = new PrintWriter(new FileWriter(FILE_USERS));
			
			file_items.close();
			file_categories.close();
			file_catitems.close();
			file_bids.close();
			file_users.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
        
        /* Process all files listed on command line. */
        for (int i = 0; i < args.length; i++) {
            File currentFile = new File(args[i]);
            processFile(currentFile);
        }
    }
}
